Appearance
前言
通过提问的方式来街道python的疑惑
python 中命名空间、作用域, 包、模块的关系
命名空间是名字到变量的映射,全局(模块)命名空间、局部(函数)命名空间和内置命名空间(各种内置变量),通过locals()、globals() 获取命名空间(会将所有的变量映射成字典)。内置命名空间不能直接获取,只能通过dir(buildIn)获取变量列表
作用域在很多语言都有,各个语言也大差不差。通俗理解就是你想看的代码文本区块能够访问命名空间有哪些。在查找某个变量是总是按照局部作用域->闭包作用域->全局作用域->内置作用域来查找。
包和模块都是一个对象,所有申明在模块的变量其实都是模块的属性,模块中内置了很多属性和方法,都是在模块加载解释器注入到其中的。
模块的初始化: 当碰到Import语句时,解释器会生成一个模块对象(实际是一个命名空间),包括全局空间和内置空间。然后添加name到模块对象中,然后依次执行代码,添加局部空间。最后import 就会得到这个对象,注意的是模块默认情况下只会导入一次
变量、对象和值
python 一切都是对象,常见的数字、字符串、函数、类、集合、列表等都是对象。对象在内存会占据一块区域,下面是对象的一个列子。
type: "int"
value: 1,
id: "xxasfsafas1231"其中对象有区分可变对象和不可变对象,不可变对象有数字、字符串、元组等。意思是创建成功后值不可变化,可变对象值得是创建成功后值是可变的。通过 id(xxx)可以获取对象的内存空间值
值其实就是对象的一个属性,第一种是对象的值空间直接保存原始的值,另外就是值空间保存其他若干对象的引用。
变量实际一个指针,保存在栈空间,总是会指向对象的内存空间上。
举一些列子来理解这些概念
值 1 和 对象 1 有啥区别, 代码中 a = 1 是啥
值 1 就是简单表示值是 1,对象 1 值得是一个对象,对象是有上面的特性,值 1 只是对象 1 的一个特性。 a = 1 首先创建了一个对象 1(如果之前没有创建过),把值设置成 1,type 是 int ,id 设置对象当前的内存地址,同时创建一个变量,变量保存在栈空间,变量的值指向了对象 1 的 id。 如果再写 b = 1,这个时候不会再创建对象 1,而是直接复用,应为是不可变类型
为什么 1 == 1, 1 is 1 == true。
弄清这个问题首先要搞清 == 是计算符,计算符的行为是可以被定义的。1 == 1 只是判断的值是不是相同,显然同一个对象的值是相同的, 1 is 1 是判断内存地址是否相同。 对于对象来说,总是全局唯一的,所以也是相同。
弄清 python 中标识符、保留标识符、关键字
曾经我以为 type 是关键字,除了常见的关键字,列一些容易搞错的。
- in
- is
- nonlocal
- global
- None
- yield
- with
保留的标识符有
__ 不会被 import _ 导入(不过我们从不用 import * 导入)
__ 双下划线用在类中会被解析器重命名成 classname__name,避免子类覆盖,但是__*|_除外(__init__)
__*__ 表示魔法方法,作为未来 python 的扩展,比如 __call__, __init__, __class__
格式化字符串
现在只推荐 f 函数,之前 format 等等都不要再使用。repr 用于输出 python 解释器识别的,str 用于输出用户识别的内容, 比如 repr("1")会输出 "1"(带引号),而 str 只会输出 1。 举例一些少见而有用的技巧
f"{a=}" 或者 {sum(2)=} // 输出 a=1 sum(2)=22
:表示格式化内容 :2f 保留两位小数(不四舍五入):.3% 保留 3 位小数的% :_ 分隔符,除了前面保留的几个特殊字符都可以。当然也能自由组合 :_.4f, :e 科学计数法 :.2e 保留两位小数的科学计数法
格式化时间 f{a:%Y/%m/%d}
文本对齐 f"a:>n" 文本前面插入空格,文本后面补齐空格
推导式
推导式通过执行一系列的循环用于快速的创建序列,按照语法糖来理解。我们要求无脑使用推导式,当然表达式性能也是有问题,会全部计算出来。不如 推导式总是按照 表达式 + 一层一层的 for 语句或者 if 语句。执行过程是 for if 执行完后执行 表示表达式,通过是
- 列表推导
- 创建 [x * 2 for x in range(5)] [x for x in range(10) if x % 2 == 0]
- 字典推导式 {x: x + 1 for x in range(5) }
切片
用的最多在列表, [start🔚step] 每个参数都可以忽略返回 start 默认列表初始 end 默认列表 len, step 默认 1,负数时需要翻转。始终是 start+step 获取下一个索引,如果 start+step 不在区间内,始终返回[] 切片总是返回一个浅拷贝,同时切片也能作为一个独立对象,表示原对象的一部分,比如 a[:0] 表示的是列表开头 a[:0] = [1,2] 这样就能插入 1,2 到列表最开始
替换元素的执行逻辑是把起始索引到解锁索引的元素删除,再添加到起始位置。
序列
字符串、元组、字节串(不可变序列),列表、字节数组(不可变序列),怎么理解不可变序列, 比如你改动 a = "abc",想把"abc"改成了"abb",一定是创建了一个新的对象,而不是在原有的对象上改动的
描述器
可迭代对象
hash 和字典的关系
字典内部使用了 hash 表的方式来查询键。
函数
函数是怎么创建出来的
函数也是一个对象,这个对象有call方法,同理函数的生成也是元类在处理
函数中有用的特殊属性,为什么会有这些特殊的属性,怎么来的
- doc 获取文档字符串, 也能获取模块的
- name 函数名称
- qualname 函数在调用时的层级名字 xx.xx.xx.name
- defaults 默认参数组元组,
- dict 命名空间中属性
- annotations 获取参数的注解
class
super 详解
class 怎么来的
当解释器碰到class的申明时,会创建一个命名空间,同时添加默认的变量name class base等,同时会添加class的元类,用于如何实例化这个类,可以看到class本身也就是一个稍微特殊的对象,我们可以自由的定义class定义和最终实例的各个步骤
type
type是所有的class默认的元类,type 本身也是一个对象,同时也是一个可执行的对象,通常type(xxx)获取对象的类型,总是会调用这个对象的类对象的元类型的call方法。按照下面来执行
- 检查obj是否有一个名为class的属性,并且这个属性是一个类对象。
- 如果满足条件,直接返回obj.class。
- 如果不满足条件,继续执行下一步。
- 检查obj的类对象是否有一个名为class的属性,并且这个属性是一个类对象。
- 如果满足条件,直接返回obj.class.class。
- 如果不满足条件,继续执行下一步。
- 返回type(obj),即obj的类对象的元类。
执行帧、代码块和小数据池
python 中的对象是怎么来的 a = 1 干了嘛
添加一个a到命名空间,直接在小数据块中获取一个对象1,然后将a执行a对应的内存空间,简单理解成a -> id(1)
with 详解
上下文管理器是一个对象,包含了__enter__ 和__exit 方法。with 语句能够自动的调用这些方法
类型系统
typing.get_type_hints 能够获取 class,function 和模块中注解,当然也默认给其提供了__annotations__属性
模拟容器
官方内置了一些容器对象,比如元组、列表、字典、
常见的变量和属性
- __dict__
- __name__
- __module__
